﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using OpenLightGroup.Common.ApplicationServices.EventsProxy;

namespace OpenLightGroup.Common.ApplicationServices
{
    public class EventLogger
    {
        #region Members
        private static List<EventLogRequest> _pendingRequests = null;
        private static bool _isServiceAvailable = false;
        private static DateTime _lastServiceHeartbeatCheck = DateTime.MinValue;
        private static Func<string> _getApplicationDetails;

        private static int _clientThrottleMaxRequests = 100;
        private static TimeSpan _clientThrottleTimeSpan = TimeSpan.FromSeconds(1);

        static EventLogger()
        {
            _getApplicationDetails = ClientApplicationInformation.GetDetails;

            NetworkDelegateManager.RegisterDelegates(new Action(CheckServiceIsAvailable),
                                                         new Action(() => { IsServiceAvailable = false; }));
        }

        /// <summary>
        /// Gets or sets the client throttle max requests.
        /// </summary>
        /// <value>The client throttle max requests.</value>
        public static int ClientThrottleMaxRequests
        {
            get
            {
                return _clientThrottleMaxRequests;
            }
            set
            {
                _clientThrottleMaxRequests = value;
            }
        }

        /// <summary>
        /// Gets or sets the client throttle time span.
        /// </summary>
        /// <value>The client throttle time span.</value>
        public static TimeSpan ClientThrottleTimeSpan
        {
            get
            {
                return _clientThrottleTimeSpan;
            }
            set
            {
                _clientThrottleTimeSpan = value;
            }
        }

        protected internal static List<EventLogRequest> PendingRequests
        {
            get
            {
                if (_pendingRequests == null)
                {
                    _pendingRequests = new List<EventLogRequest>();
                }
                return _pendingRequests;
            }
        }

        protected static bool IsServiceAvailable
        {
            get
            {
                if (DateTime.Now - _lastServiceHeartbeatCheck > TimeSpan.FromMinutes(1))
                {
                    //Prevents multiple checks in a row
                    _lastServiceHeartbeatCheck = DateTime.Now;
                    CheckServiceIsAvailable();
                }
                return _isServiceAvailable;
            }
            private set
            {
                //Extends the time needed between checks
                _lastServiceHeartbeatCheck = DateTime.Now;
                _isServiceAvailable = value;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Allows the consuming application to override the default method to get the application details.
        /// </summary>
        /// <param name="function">
        /// The method to execute when application details are needed.
        /// </param>
        public static void RegisterGetApplicationDetailsDelegate(Func<string> function)
        {
            if (function != null)
                _getApplicationDetails = function;
        }

        public static void Debug(string message)
        {
            var request = QueueRequest(ClientEventTypes.Debug, message, null, 0);

            if (request != null && IsServiceAvailable)
            {
                var client = new EventLoggerServiceClient();

                client.DebugCompleted += new EventHandler<AsyncCompletedEventArgs>(DebugCompleted);

                client.DebugAsync(request, request.Id);
            }
        }

        public static void LogError(Exception ex)
        {
            var request = QueueRequest(ex);

            if (request != null && IsServiceAvailable)
            {
                var client = new EventLoggerServiceClient();
                client.LogErrorCompleted += new EventHandler<AsyncCompletedEventArgs>(LogErrorCompleted);
                client.LogErrorAsync(request, request.Id);
            }
        }

        public static void LogWarning(string message)
        {
            LogWarning(message, null, 0);
        }

        public static void LogWarning(string message, object source, int detailCode)
        {
            Log(ClientEventTypes.Warning, message, source, detailCode);
        }

        public static void LogInformation(string message)
        {
            LogInformation(message, null, 0);
        }

        public static void LogInformation(string message, object source, int detailCode)
        {
            Log(ClientEventTypes.Information, message, source, detailCode);
        }

        protected static void ProcessPendingRequests()
        {
            //PendingRequests.ForEach(e => Log(e));
            foreach (var item in PendingRequests)
            {
                var client = new EventLoggerServiceClient();

                if (item.Exception != null)
                {
                    client.LogErrorCompleted += new EventHandler<AsyncCompletedEventArgs>(LogErrorCompleted);

                    client.LogErrorAsync(item, item.Id);
                }
                else
                {
                    client.LogCompleted += new EventHandler<LogCompletedEventArgs>(LogCompleted);

                    client.LogAsync(item, item.Id);
                }
            }
        }

        #endregion

        #region PendingRequests

        private static EventLogRequest GetRequest(Guid id)
        {
            return PendingRequests.SingleOrDefault(ev => ev.Id == id);
        }

        private static EventLogRequest QueueRequest(Exception ex)
        {
            return QueueRequest(ClientEventTypes.Error, ex.Message, ex.StackTrace, 0, ex, null);
        }

        private static EventLogRequest QueueRequest(ClientEventTypes clientEventTypes, string message, object source, int detailCode)
        {
            return QueueRequest(clientEventTypes,
                                message,
                                source,
                                detailCode,
                                null,
                                String.Empty);
        }


        
        private static EventLogRequest QueueRequest(ClientEventTypes eventType, string message, object source, int detailCode, Exception ex, string details)
        {

            var tooManyRequests = PendingRequests.Where
                (r => (DateTime.Now - r.EventTime) <= ClientThrottleTimeSpan).Count() > ClientThrottleMaxRequests;

            if (tooManyRequests)
            {
                System.Diagnostics.Debug.WriteLine("Dropped event log request: " + message);
                return null;
            }
            else
            {
                EventLogRequest request = new EventLogRequest(details, ex, _getApplicationDetails)
                                          {
                                              Message = message,
                                              EventType = eventType,
                                              Id = Guid.NewGuid(),
                                              EventTime = DateTime.Now,
                                              EventDetailCode = detailCode,
                                              EventSource = source,
                                          };

                PendingRequests.Add(request);

                return request;
            }
        }

        private static void DequeueRequest(Guid id)
        {
            PendingRequests.Remove(GetRequest(id));
        }

        #endregion

        #region Protected Methods

        protected internal static void CheckServiceIsAvailable()
        {
            var client = new EventLoggerServiceClient();

            client.IsAliveCompleted += new EventHandler<IsAliveCompletedEventArgs>(
            CheckSerivceIsAvailableCompleted);

            client.IsAliveAsync();
        }

        protected internal static void Log(EventLogRequest request)
        {
            if (request != null && IsServiceAvailable)
            {
                var client = new EventLoggerServiceClient();

                client.LogCompleted += new EventHandler<LogCompletedEventArgs>(LogCompleted);

                client.LogAsync(request, request.Id);
            }
        }

        protected internal static void Log(ClientEventTypes eventType,
                                           string message,
                                           object source,
                                           int detailCode)
        {
            var request = QueueRequest(eventType, message, source, detailCode);

            Log(request);
        }
        #endregion

        #region Event Handlers

        protected static void CheckSerivceIsAvailableCompleted(object sender, IsAliveCompletedEventArgs e)
        {
            if (_isServiceAvailable == false && e.Result == true)
                ProcessPendingRequests();

            IsServiceAvailable = e.Result;
        }

        protected static void LogCompleted(object sender, LogCompletedEventArgs e)
        {
            UpdateIsServiceAvailableHelper(e.Error);

            var client = sender as EventLoggerServiceClient;

            client.LogCompleted -= new EventHandler<LogCompletedEventArgs>(LogCompleted);

            var id = new Guid(e.UserState.ToString());

            if (e.Cancelled == false && e.Error == null)
            {
                DequeueRequest(id);
            }
            else if (e.Error != null)
            {
                LogError(e.Error);
            }
            else if (e.Cancelled)
            {
                Log(ClientEventTypes.Warning,
                    "Logging request has been cancelled.",
                    GetRequest(id),
                    5);
            }
        }

        protected static void LogErrorCompleted(object sender, AsyncCompletedEventArgs e)
        {
            UpdateIsServiceAvailableHelper(e.Error);

            var client = sender as EventLoggerServiceClient;

            client.LogErrorCompleted -= new EventHandler<AsyncCompletedEventArgs>(LogErrorCompleted);

            var id = new Guid(e.UserState.ToString());

            if (e.Cancelled == false && e.Error == null)
            {
                DequeueRequest(id);
            }
            else if (e.Error != null)
            {
                //ToDo prevent infinite loop here!
                //LogError(e.Error);
            }
            else if (e.Cancelled)
            {
                Log(ClientEventTypes.Warning,
                    "Logging request has been cancelled.",
                    GetRequest(id),
                    5);
            }
        }

        protected static void DebugCompleted(object sender, AsyncCompletedEventArgs e)
        {
            UpdateIsServiceAvailableHelper(e.Error);

            var client = sender as EventLoggerServiceClient;

            client.DebugCompleted -= new EventHandler<AsyncCompletedEventArgs>(DebugCompleted);

            var id = new Guid(e.UserState.ToString());

            if (e.Cancelled == false && e.Error == null)
                DequeueRequest(id);
        }

        #endregion

        private static void UpdateIsServiceAvailableHelper(Exception e)
        {
            //Hack but should help cut down on useless traffic.
            if (e == null)
                IsServiceAvailable = true;
        }
    }
}